#include "check_room.h"
#include <stdlib.h>
#include <stdint.h>
#include <sys/time.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/select.h>
#include <errno.h>
#endif

struct check_room_t {
#ifdef _WIN32
    HANDLE evt;
#else
    int efd;
#endif
};

/* 获取当前毫秒时间 */
static uint64_t now_ms() {
#ifdef _WIN32
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    uint64_t t = (((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10000;
    return t;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}

/* 创建 check_room */
check_room_t* cr_create() {
    check_room_t* cr = (check_room_t*)malloc(sizeof(check_room_t));
    if(!cr) return NULL;

#ifdef _WIN32
    cr->evt = CreateEvent(NULL, TRUE, FALSE, NULL); // manual reset
    if(!cr->evt) { free(cr); return NULL; }
#else
    cr->efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if(cr->efd < 0) { free(cr); return NULL; }
#endif

    return cr;
}

/* 销毁 check_room */
void cr_destroy(check_room_t* cr) {
    if(!cr) return;
#ifdef _WIN32
    if(cr->evt) CloseHandle(cr->evt);
#else
    close(cr->efd);
#endif
    free(cr);
}

/* 阻塞等待 */
int cr_wait(check_room_t* cr, int ms) {
    if(!cr) return -1;
#ifdef _WIN32
    DWORD timeout = (ms < 0) ? INFINITE : (DWORD)ms;
    DWORD ret = WaitForSingleObject(cr->evt, timeout);
    if(ret == WAIT_OBJECT_0) return 1;
    else if(ret == WAIT_TIMEOUT) return 0;
    else return -1;
#else
    if(ms < 0) {
        uint64_t tmp;
        return (read(cr->efd, &tmp, sizeof(tmp)) == sizeof(tmp)) ? 1 : -1;
    } else {
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(cr->efd, &fds);
        struct timeval tv = { ms/1000, (ms%1000)*1000 };
        int ret = select(cr->efd+1, &fds, NULL, NULL, &tv);
        if(ret > 0) { uint64_t tmp; read(cr->efd, &tmp, sizeof(tmp)); return 1; }
        else if(ret == 0) return 0;
        else return -1;
    }
#endif
}

/* 唤醒等待者 */
void cr_notify(check_room_t* cr) {
    if(!cr) return;
#ifdef _WIN32
    SetEvent(cr->evt);
#else
    uint64_t one = 1;
    write(cr->efd, &one, sizeof(one));
#endif
}

/* wait_for */
int cr_wait_for(check_room_t* cr, cr_pred_t pred, void* param, int ms) {
    if(!cr || !pred) return -1;

    uint64_t end_time = (ms < 0) ? 0 : now_ms() + ms;
    while(1) {
        if(pred(param)) return 1;
        int wait_ms = ms;
        if(ms >= 0) {
            int64_t rem = (int64_t)(end_time - now_ms());
            if(rem <= 0) return 0;
            wait_ms = (int)rem;
        }
        int r = cr_wait(cr, wait_ms);
        if(r < 0) return -1;
        if(r == 0 && ms >= 0) return 0;
    }
}

/* wait_until */
int cr_wait_until(check_room_t* cr, cr_pred_t pred, void* param, uint64_t end_time_ms) {
    if(!cr || !pred) return -1;
    while(1) {
        if(pred(param)) return 1;
        int64_t rem = (int64_t)(end_time_ms - now_ms());
        if(rem <= 0) return 0;
        int r = cr_wait(cr, (int)rem);
        if(r < 0) return -1;
    }
}
